/*
 * Copyright (C) 2022 . All rights reserved.
 *
 * File name   : Range.hpp
 * Author      : longbin
 * Created date: 2022-11-30 11:02:38
 * Description : provide range() like python range(start, end, step) for loop.
 *
 */

#ifndef __RANGE_HPP__
#define __RANGE_HPP__

#include <iostream>

namespace range
{
template <class T>
class Range
{
private:
    const T mBegin;
    const T mEnd;
    const T mStep;
    T mStepCount;

    class Iterator
    {
    private:
        T mCurrentStep;
        const Range &mRange;
        T mCurrentValue;

    public:
        Iterator(const T &start, const Range &range) : mCurrentStep(start), mRange(range)
        {
            mCurrentValue = mRange.mBegin + (mCurrentStep * mRange.mStep);
        }

        T operator*() const
        {
            return mCurrentValue;
        }

        bool operator!=(const Iterator &another) const
        {
            return mCurrentStep != another.mCurrentStep;
        }

        const Iterator *operator++()
        {
            mCurrentStep += 1;
            mCurrentValue += mRange.mStep;
            return this;
        }
    }; // class Iterator.

public:
    explicit Range(const T &begin, const T &end, const T &step = 1)
        : mBegin(begin), mEnd(end), mStep(step)
    {
        // static_assert(step != 0);
        // static_assert((end - begin) / step >= 0);

        if (0 == mStep) {
            throw std::invalid_argument("ValueError: range() arg 3 cannot be 0.");
        }

#if 0
        mStepCount = (mEnd - mBegin) / mStep;
        if (mStepCount < 0) {
            // throw std::invalid_argument("ValueError: range() args invalid.");
            // invalid
            mStepCount = 0;
        } else {
            // ok.
            // range(1,5,2): (5-1)/2=2, range:1,3
            // range(1,6,2): (6-1)/2=2, actually step count is 2+1=3, range:1,3,5
            if ((mStepCount * mStep) != mEnd - mBegin) {
                mStepCount += 1;
            }
        }
#else
        mStepCount = 1 + (mEnd - ((mStep > 0) ? (1) : (-1)) - mBegin) / mStep;
        if (mStepCount < 0) {
            mStepCount = 0;
        }
#endif
    };

    Iterator begin()
    {
        return Iterator(0, *this);
    }

    Iterator end()
    {
        return Iterator(mStepCount, *this);
    }

}; // class Range

template <typename T, typename U = int>
auto range(const T &begin, const T &end, const U &step = 1) -> Range<decltype(begin + step)>
{
    return Range<decltype(begin + step)>(begin, end, step);
};

template <typename T>
Range<T> range(const T &end)
{
    return Range<T>(0, end, 1);
};
}; // namespace range

#ifndef RANGE_TEST
#    define RANGE_TEST 0
#endif // RANGE_TEST

#if RANGE_TEST

#    ifdef LOG_TAG
#        undef LOG_TAG
#    endif
#    define LOG_TAG "Range"

void range_test()
{
    for (const auto &seq : range::range(5)) {
        LOGI("range(5): %d", seq); // 0,1,2,3,4
    }
    for (const auto &seq : range::range(1, 1)) {
        LOGI("range(1,1): %d", seq); // null
    }
    for (const auto &seq : range::range(1, 5)) {
        LOGI("range(1,5): %d", seq); // 1,2,3,4
    }
    for (const auto &seq : range::range(1, 5, 1)) {
        LOGI("range(1,5,1): %d", seq); // 1,2,3,4
    }
    for (const auto &seq : range::range(1, 5, 2)) {
        LOGI("range(1,5,2): %d", seq); // 1,3
    }
    for (const auto &seq : range::range(5, 1, -1)) {
        LOGI("range(5,1,-1): %d", seq); // 5,4,3,2
    }
    for (const auto &seq : range::range(5, 1, -2)) {
        LOGI("range(5,1,-2): %d", seq); // 5,3
    }
    for (const auto &seq : range::range(5, -1)) {
        LOGI("range(5,-1): %d", seq); // null
    }
    for (const auto &seq : range::range(1, 2, 2)) {
        LOGI("range(1,2,2): %d", seq); // 1
    }
    for (const auto &seq : range::range(1, 10, 2)) {
        LOGI("range(1,10,2): %d", seq); // 1,3,5,7,9
    }
    for (const auto &seq : range::range(1, 10, 3)) {
        LOGI("range(1,10,3): %d", seq); // 1,4,7
    }
    // for (const auto &seq : range::range(1, 10, 0)) {
    //     LOGI("range(10,1,0): %d", seq); // fatal error
    // }
    for (const auto &seq : range::range(1, 10, -1)) {
        LOGI("range(1,10,-1): %d", seq); // null
    }
    for (const auto &seq : range::range(10, 1, -1)) {
        LOGI("range(10,1,-1): %d", seq); // 10,9,8,7,6,5,4,3,2
    }
}
#endif // RANGE_TEST

#endif //__RANGE_HPP__
